package inglar.autotc.client.ui;

import inglar.autotc.client.HasContainingTreeItem;
import inglar.autotc.client.resource.ResourceHandler;
import inglar.autotc.shared.Values;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.TreeItem;
import com.google.gwt.user.client.ui.VerticalPanel;

/**
 * A renderable representation of a tc filter. 
 * @author Lee
 */
public class NodeFilter extends Composite implements HasContainingTreeItem {
	
//	private String script = "initscript";
	
	private String parent = "initparent";
	private String flowid = "initflowid";
	
	private String filter = "initfilter";
	private String options = "initopts opts";
	
	private String device = "initdevice";
	private String action = "initaction";
	
	private String pref = "initpref"; // may be prio
	private String protocol = "ip"; // we're only dealing with ip for now
	
	private HorizontalPanel contentPanel;
	private Image qicon;
	private HTML lblFlowid; // classid
	private HTML lblParent;
	private HTML lblFilter;
	private HTML lblPref;
	private HTML lblProtocol;
	
	private VerticalPanel varopts;
	private HorizontalPanel buttons;
	
	PageDevices tsRef = null;
	NodeFilter self = null;
	private TreeItem container = null;

	public NodeFilter() {
		contentPanel = new HorizontalPanel();
		initWidget(contentPanel);
		contentPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
		contentPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);
		
		qicon = new Image(ResourceHandler.INSTANCE.fyellow16());
		contentPanel.add(qicon);
		qicon.getElement().setClassName("nodeIcon");
		
		VerticalPanel p1 = new VerticalPanel();
		contentPanel.add(p1);
		p1.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_LEFT);
		p1.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);
		
		lblFilter = new HTML();
		p1.add(lblFilter);
		lblFilter.getElement().setClassName("tsNodeLabel");
		
		HorizontalPanel p3 = new HorizontalPanel();
		p1.add(p3);
		
		VerticalPanel p5 = new VerticalPanel();
		p3.add(p5);
		p5.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_LEFT);
		
		lblFlowid = new HTML();
		p5.add(lblFlowid);
		lblFlowid.getElement().setClassName("tsNodeLabel");
		
		lblParent = new HTML();
		p5.add(lblParent);
		lblParent.getElement().setClassName("tsNodeLabel");
		
		VerticalPanel p4 = new VerticalPanel();
		p3.add(p4);
		p4.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_LEFT);
		p4.getElement().setAttribute("style", "margin-left: 10px;");
		
		lblPref = new HTML();
		p4.add(lblPref);
		lblPref.getElement().setClassName("tsNodeLabel");
		
		lblProtocol = new HTML();
		p4.add(lblProtocol);
		lblProtocol.getElement().setClassName("tsNodeLabel");

		varopts = new VerticalPanel();
		contentPanel.add(varopts);
		varopts.getElement().setAttribute("style", "margin-left: 15px;");
		
		buttons = new HorizontalPanel();
		contentPanel.add(buttons);
		buttons.getElement().setAttribute("style", "margin-left: 15px;");
		
		VerticalPanel p21 = new VerticalPanel();
		buttons.add(p21);
		
		Button btnDelete = new Button("DELETE");
		p21.add(btnDelete);
		btnDelete.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
					container.remove();
			}
		});
		btnDelete.getElement().setAttribute("style", "font-weight: bold; font-size: xx-small;");
		
		Button btnModify = new Button("MODIFY");
		p21.add(btnModify);
		btnModify.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				DialogBoxEditNodeFilter nfe = new DialogBoxEditNodeFilter();
				nfe.setTargetNode(self);
				nfe.center();
//				nfe.txtbxFlowId.setEnabled(false);
				nfe.txtbxParent.setEnabled(false);
//				nfe.txtbxPriority.setEnabled(false);
				nfe.txtbxProtocol.setEnabled(false);
			}
		});
		btnModify.getElement().setAttribute("style", "font-weight: bold; font-size: xx-small;");
		
		this.getElement().setClassName("tsNodeFilter");
		
		self = this;
	}

	public NodeFilter(String tcCmd) {
		this();
		this.setScript(tcCmd);
	}

	/**
	 * @return the parent
	 */
	public String getParentId() {
		return parent;
	}

	/**
	 * @param parent the parent to set
	 */
	public void setParentId(String parent) {
		this.parent = parent;
		lblParent.setHTML("parent <b>" + parent + "</b>");
	}

	/**
	 * @return the flowid
	 */
	public String getFlowid() {
		return flowid;
	}

	/**
	 * @param flowid the flowid to set
	 */
	public void setFlowid(String flowid) {
		this.flowid = flowid;
		lblFlowid.setHTML("flowid <b>" + flowid + "</b>");
	}

	/**
	 * @return the filter
	 */
	public String getFilter() {
		return filter;
	}

	/**
	 * @param filter the filter to set
	 */
	public void setFilter(String filter) {
		this.filter = filter;
		if(filter.isEmpty()) lblFilter.setHTML("filter type <i>(none specified)</i>");
		else lblFilter.setHTML("filter type <b>" + filter + "</b>");
	}

	/**
	 * @return the options
	 */
	public String getOptions() {
		return options;
	}

	/**
	 * @param options the options to set
	 */
	public void setOptions(String options) {
		
		String displayhtml = "";
		String[] o = options.split("match");
		for(int i = 0; i < o.length; i++) {
			if(o[i].trim().isEmpty()) continue;
			else displayhtml += "match " + o[i] + "<br/> ";
		}
		
		this.options = options;
		HTML lblOptions = new HTML(displayhtml);
		varopts.clear();
		varopts.add(lblOptions);
		lblOptions.getElement().setClassName("tsNodeLabel");
	}

	/**
	 * @return the device
	 */
	public String getDevice() {
		return device;
	}

	/**
	 * @param device the device to set
	 */
	public void setDevice(String device) {
		this.device = device;
	}

	/**
	 * @return the action
	 */
	public String getAction() {
		return action;
	}

	/**
	 * @param action the action to set
	 */
	public void setAction(String action) {
		this.action = action;
	}

	/**
	 * @return the pref
	 */
	public String getPref() {
		return pref;
	}

	/**
	 * @param pref the pref to set
	 */
	public void setPref(String pref) {
		this.pref = pref;
		lblPref.setHTML("priority <b>" + pref + "</b>");
	}

	/**
	 * @return the protocol
	 */
	public String getProtocol() {
		return protocol;
	}

	/**
	 * @param protocol the protocol to set
	 */
	public void setProtocol(String protocol) {
		this.protocol = protocol;
		lblProtocol.setHTML("protocol <b>" + protocol + "</b>");
	}

	/**
	 * @return the script
	 */
	public String getScript() {
		String f = getFilter(), retval = "";
		if(!f.trim().isEmpty()) {
			retval = "tc filter add dev ";
			retval += getDevice() + " ";
			retval += "parent " + getParentId() + " ";
			retval += "protocol " + getProtocol() + " ";
			retval += "pref " + getPref() + " ";
			retval += getFilter() + " ";
			retval += getOptions() + " ";
			retval += "flowid " + getFlowid();
		}
		return retval.trim();
	}

	/**
	 * @param str the script to set
	 */
	public void setScript(String str) {
		String script = str.trim();

		String[] v = script.split(Values.REGEX_ALL_WHITESPACE);
		int i = 0;
		if(!v[i++].equals("tc")) throw new IllegalArgumentException("Argument script not a tc command");
		if(!v[i++].equals("filter")) throw new IllegalArgumentException("Argument script not a tc filter command");
		
		if(!v[i].equals("dev")) {
			String temp = v[i++];
			if(temp.equals("add")
//					|| temp.equals("del") || temp.equals("replace") || temp.equals("change") || temp.equals("show")
					) this.setAction(temp);
			else throw new IllegalArgumentException("Argument script has an invalid or unwanted optional parameter (" + temp + ") before dev");
		}
		
		if(!v[i++].equals("dev")) throw new IllegalArgumentException("Argument script does not have a dev parameter where expected");
		if(v[i].equals("pref") || v[i].equals("root") || v[i].equals("ingress") || v[i].equals("parent") || v[i].equals("pref")) 
			throw new IllegalArgumentException("Argument script has unsafe dev parameter value");
		else this.setDevice(v[i++]);
		
		int posParent = -1, posProto = -1, posPref = -1; 
		boolean getParent = false, getProto = false, getPref = false;
		for( ; i < v.length; i++) {
			if(v[i].equals("pref") || v[i].equals("prio")) {
				if(posPref < 0) {
					if(getParent || getProto || getPref) throw new IllegalArgumentException("Argument script has unexpected token ('" + v[i] + "')");
					getPref = true;
					posPref = i;
				} else throw new IllegalArgumentException("Argument script already has pref parameter");
			} else if(v[i].equals("parent")) {
				if(posParent < 0) {
					if(getParent || getProto || getPref) throw new IllegalArgumentException("Argument script has unexpected token ('parent')");
					getParent = true;
					posParent = i;
				} else throw new IllegalArgumentException("Argument script already has parent attachment");
			} else if(v[i].equals("protocol")) {
				if(posProto < 0) {
					if(getParent || getProto || getPref) throw new IllegalArgumentException("Argument script has unexpected token ('protocol')");
					getProto = true;
					posProto = i;
				} else throw new IllegalArgumentException("Argument script already has protocol parameter");
			} else {
				if(getParent) {
					this.setParentId(v[i]);
					getParent = false;
				} else if(getProto) {
					this.setProtocol(v[i]);
					getProto = false;
				} else if(getPref) {
					this.setPref(v[i]);
					getPref = false;
				} else break; // i must be pointing to FILTER_TYPE
			}
		}
		
		// here, i must be pointing to FILTER_TYPE or flowid/classid
		if(isFilter(v[i])) {
			this.setFilter(v[i++]);
			
			String opts = "";
			for( ; i < v.length; i++) {
				if(v[i].equals("flowid") || v[i].equals("classid")) {
					i++;
					this.setFlowid(v[i]);
				} else {
					opts += v[i] + " ";
				}
			}
			this.setOptions(opts.trim());
			
		} else if(v[i].equals("flowid") || v[i].equals("classid")) {
			i++;
			this.setFilter("");
			this.setFlowid(v[i]);
			this.setOptions("");
		} else throw new IllegalArgumentException("Argument script has an unknown filter type '" + v[i] + "'");
	}
	
	public static String[] filters = {
//			"rsvp",
//			"route",
//			"fw",
//			"u8",
//			"u16", 
			"u32" 
		};
	
	/**
	 * Determines whether a given string is a filter supported by the application.
	 * @param str filter
	 * @return <b>true</b> when parameter is a supported filter; <b>false</b> otherwise
	 */
	public boolean isFilter(String str) {
		for(int i = 0; i < filters.length; i++) {
			if(filters[i].equals(str)) return true;
		}
		return false;
	}
	
	/**
	 * @return the container
	 */
	public TreeItem getContainer() {
		return container;
	}

	/**
	 * @param container the container to set
	 */
	public void setContainer(TreeItem container) {
		this.container = container;
//		container.setState(true);
	}
	
}

/*
# tc filter help
Usage: tc filter [ add | del | change | replace | show ] dev STRING
       [ pref PRIO ] protocol PROTO ------------------------------------------- BUT protocol can be swapped positions with pref/prio and parent 
       [ estimator INTERVAL TIME_CONSTANT ] ----------------------------------- assumed not to be in script
       [ root | classid CLASSID ] [ flowid FILTERID ] ------------------------- BUT flowid and classid expected after FILTER_TYPE, and root is considered 
       [ [ FILTER_TYPE ] [ help | OPTIONS ] ]                                       as duplicate of parent, classid duplicate of flowid

       tc filter show [ dev STRING ] [ root | parent CLASSID ]
Where:
FILTER_TYPE := { rsvp | u32 | fw | route | etc. }
FILTERID := ... format depends on classifier, see there
OPTIONS := ... try tc filter add <desired FILTER_KIND> help
#

BUT - with htb, tried to do "tc filter ... root ..." instead of "tc filter ... parent 1:0 ..." (where 1:0 is the handle of the qdisc at root)
and got an error, "RTNETLINK answers: Invalid argument We have an error talking to the kernel"

So new (htb only?) syntax:
tc filter ACTION dev DEV [ pref PRIO ] [ protocol PROTO ] [ parent PARENT ]
		[ estimator INTERVAL TIME_CONSTANT ] ---------------------------------- assumed not to be in script
		[ [ FILTER_TYPE ] [ help | OPTIONS ] ] -------------------------------- will handle u32 only
		[ [ flowid | classid ] TARGET_CLASS ] --------------------------------- flowid/classid *must* come after FILTER_TYPE
Where:
ACTION := add | del | change | replace | show
OPTIONS := FILTER_TYPE_OPTIONS

*/